package com.zhao.service.impl;

import java.util.Iterator;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.google.common.base.Objects;
import com.zhao.dao.LineDetailMapper;
import com.zhao.dao.LineInfoMapper;
import com.zhao.dao.PointInfoMapper;
import com.zhao.exception.ErrorAddressException;
import com.zhao.exception.ErrorLineException;
import com.zhao.po.LineDetail;
import com.zhao.po.LineInfo;
import com.zhao.po.OrderInfo;
import com.zhao.po.PointInfo;
import com.zhao.po.PointInfoExample;
import com.zhao.po.PointInfoExample.Criteria;
import com.zhao.service.LineService;
import com.zhao.utils.MyWeightedGraph;
import com.zhao.utils.SimilarityUtil;

@Service
public class LineServiceImpl implements LineService {

	@Autowired
	private LineDetailMapper lineDetailMapper;

	@Autowired
	private LineInfoMapper lineInfoMapper;

	@Autowired
	private PointInfoMapper pointInfoMapper;
	/**
	 * 要实现路径的选择需要注意下面的问题： 所有点之间的距离，费用都是存起来的。每次需要发货的时候，可以通过计算，得到最佳的路线。
	 * 这个路线可以放在表中记录起来，以便下次使用。 如果基础表中某两点间的权值发生变化时，所有与之相关的路线应被打上标记，在使用时重新计算。
	 * 路线的设计，以费用为优先，看需不需要考虑以时间为优先的因素。
	 * 
	 * (这个最短路径先存储在数据库中)
	 */

	/**
	 * 这个方法是要用来要我们选取最优路线的
	 * 
	 * 这里我们需要的起点和终点怎么传递过来
	 * 
	 * 终点可以和订单一起传送过来。但是起点怎么获得? 起点的一般是仓库的地址
	 * 
	 * @return
	 * @throws ErrorAddressException
	 */
	@Override
	public LineDetail choseLine(String start, OrderInfo order) throws ErrorAddressException {

		// （因为查询的视乎需要的是整形的pointid，所以需要方法将传入的字符串地址转换成int的id）
		int startedPoint = findPoint(start); //这里到时候再根据业务需求修改
				
		PointInfo end = new PointInfo();
		end.setPointcity(order.getReceivercity());
		end.setPointdistrict(order.getReceiverdistrict());
		end.setPointadress(order.getReceiveraddress());
		int endPoint = findPoint(end);

		if (startedPoint == -1 || endPoint == -1) {
			// 没有找到对应的地址
			throw new ErrorAddressException("错误的运送地址");
		}

		// 0.查询我们存储的最短路径中有没有我们要查的
		LineDetail minLine = getMinRoute(startedPoint, endPoint);
		if (minLine != null) {
			return minLine;
		} else {
			// 1.存储处没有就取最短路径，就通过算算出最短路径
			LineDetail computeLine = computeLine(startedPoint, endPoint);

			// 2.我们获取了最短的路径，我们需要将这个最短路径记录下来，下次再选择这两个地点的时候我们直接拿这个，就不需要去算了，节约时间
			lineDetailMapper.insertSelective(computeLine);
			return computeLine;
			// 3.这个最短记录我们存储在数据库中，但是这里我们需要监控他们的权值是否变化，当权值变化后清空我们已经计算了的最短路径
		}
	}

	/**
	 * 这个方法是用来修改路线的。 这里我们默认的只修改路线信息的权重， 不修改路线的起点和终点(如果修改了起点和终点了，这条路线信息就没有存在的价值了)
	 * 这里需要确定修改的line确认传回了id
	 */
	@Override
	public void updateLine(LineInfo line) {
		// TODO Auto-generated method stub
		// 1.修改路线的权重
		lineInfoMapper.updateByPrimaryKeySelective(line);
		// 2.权重改变了，那么之前计算的路径就需要重新计算。清空之前存储的记录
		clearLineCache();

	}

	/**
	 * 添加路线
	 * 
	 * @throws ErrorLineException
	 */
	@Override
	public void addLine(LineInfo line) throws ErrorLineException {
		// 这里需要判断line的正确性： 判定条件：开始点和结束点不能相同
		if (line.getLinestart().equals(line.getLineend())) {
			throw new ErrorLineException("路线的起点和终点不能相同");
		}
		lineInfoMapper.insert(line);
		clearLineCache();
	}

	/**
	 * 清空我们计算的最短路径
	 */
	private void clearLineCache() {
		lineDetailMapper.deleteByExample(null);
	}

	/**
	 * 通过算法去获取到最短路径
	 * 
	 * @param startedPoint
	 * @param endPoint
	 * @return
	 */
	private LineDetail computeLine(int startedPoint, int endPoint) {
		// TODO Auto-generated method stub
		// 1.要使用算法获取最短的路径信息就要查询出所有的LineInfo
		List<LineInfo> roadLines = lineInfoMapper.selectByExample(null);
		// 2.使用算法工具类来获取最短的路径信息
		MyWeightedGraph graph = new MyWeightedGraph(roadLines, MyWeightedGraph.LENGTH);
		return graph.getMinStep(startedPoint, endPoint);
	}

	/**
	 * 这个方法是去我们存储的最短路径里找是否存在有已经存在的最短路径，
	 * 
	 * @param startedPoint
	 * @param endPoint
	 * @return
	 */
	private LineDetail getMinRoute(int startedPoint, int endPoint) {
		// TODO
		return lineDetailMapper.getMinRoute(startedPoint, endPoint);
	}

	/**
	 * 根据传入的地址，查询出这个地址对应的pointid (这个方法估计有问题，后面再改)
	 * 这里匹配的是订单表的receiverAddress收货人详细地址
	 * 
	 * @param point
	 * @return
	 */
	private int findPoint(String point) {
		// 这里有个问题： 这里的point是怎样的值，应该怎样和point记录进行比较？
		PointInfoExample pointInfoExample = new PointInfoExample();
		Criteria pointCriteria = pointInfoExample.createCriteria();
		pointCriteria.andPointcityLike(point).andPointdistrictLike(point).andPointadressLike(point);
		List<PointInfo> pointList = pointInfoMapper.selectByExample(pointInfoExample);
		if (pointList.size() >= 1) {
			Iterator<PointInfo> it = pointList.iterator();
			PointInfo minpoint = (PointInfo) it.next();
			int min = SimilarityUtil.ld(point, minpoint.getPointadress());
			while (it.hasNext()) {
				PointInfo temp = (PointInfo) it.next();
				int tempint = SimilarityUtil.ld(point, temp.getPointadress());
				if (tempint < min) {
					min = tempint;
					minpoint = temp;
				}
			}
			return minpoint.getPointid();
		}

		return -1;
	}

	/**
	 * 这里是考虑对上面的方法来进行一下优化
	 * 
	 * @param point
	 *            思考： 这里传入的肯定是起始点或终点的地址。那么这个地址是怎么来的？
	 *            这地址是从订单中取出来，这里只需要取出订单要发送的市或者区就可以了
	 * 
	 * @return
	 */
	private int findPoint(PointInfo point) {
		// 这里有个问题： 这里的point是怎样的值，应该怎样和point记录进行比较？
		PointInfoExample pointInfoExample = new PointInfoExample();
		Criteria pointCriteria = pointInfoExample.createCriteria();
		pointCriteria.andPointcityEqualTo(point.getPointcity()).andPointdistrictEqualTo(point.getPointdistrict())
				.andPointadressLike(point.getPointadress());
		List<PointInfo> pointList = pointInfoMapper.selectByExample(pointInfoExample);

		// 这里我们我们判定根据这个条件查找出来的只会有一条记录,否则返回-1
		return pointList == null ? -1 : (Objects.equal(pointList.size(), 1) ? pointList.get(0).getPointid() : -1);
	}



}
